/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ip0region_in.h"

#include "byteorder.h"
#include "wbyte.h"

#ifndef TLS
#if ( defined __linux__ ) || ( defined __unix ) || ( defined _AIX )
#define TLS             __thread
#elif ( defined _WIN32 )
#define TLS             __declspec(thread)
#endif
#endif

TLS char	__libip0region_start_ip_cache[ 39 + 1 ] = "" ;
TLS char	__libip0region_end_ip_cache[ 39 + 1 ] = "" ;

struct Ip0regionContext
{
	char		*image_filename ;
	size_t		image_filesize ;
	char		*image_buffer ;
	
	uint16_t	version ;
	uint32_t	region_section_size ;
	uint32_t	ip_section_size ;
	
	size_t		ip_count ;
} ;

char *StrdupEntireIpRegionFile( char *pathfilename , size_t *p_file_size )
{
	struct stat	st ;
	char		*file_content = NULL ;
	size_t		file_size ;
	FILE		*fp = NULL ;
	
	int		nret = 0 ;
	
	nret = stat( pathfilename , & st ) ;
	if( nret == -1 )
	{
		return NULL;
	}
	file_size = (int)(st.st_size) ;
	
	file_content = (char*)malloc( file_size+1 ) ;
	if( file_content == NULL )
	{
		return NULL;
	}
	memset( file_content , 0x00 , file_size+1 );
	
	fp = fopen( pathfilename , "rb" ) ;
	if( fp == NULL )
	{
		return NULL;
	}
	
	nret = (int)fread( file_content , file_size , 1 , fp ) ;
	if( nret != 1 )
	{
		return NULL;
	}
	
	fclose( fp );
	
	if( p_file_size )
		(*p_file_size) = file_size ;
	return file_content;
}

int LoadIp0regionImage( char *image_filename , struct Ip0regionContext **ctx )
{
	uint16_t	*p_u16 = NULL ;
	uint32_t	*p_u32 = NULL ;
	
	if( ctx == NULL )
		return IP0REGION_ERROR_PARAMETER;
	
	(*ctx) = (struct Ip0regionContext *)malloc( sizeof(struct Ip0regionContext) ) ;
	if( (*ctx) == NULL )
		return IP0REGION_ERROR_ALLOC;
	
	(*ctx)->image_filename = STRDUP( image_filename ) ;
	if( (*ctx)->image_filename == NULL )
		return IP0REGION_ERROR_ALLOC;
	
	(*ctx)->image_buffer = StrdupEntireIpRegionFile( (*ctx)->image_filename , & ((*ctx)->image_filesize) ) ;
	if( (*ctx)->image_buffer == NULL )
		return IP0REGION_ERROR_READ_FILE;
	
	if( (*ctx)->image_filesize >= 4 && MEMCMP( (*ctx)->image_buffer , == , IP0REGION_FILEHEADER_MAGIC_STRING , 4 ) )
		;
	else
		return IP0REGION_ERROR_FILEHEADER_MAGIC;
	
	if( (*ctx)->image_filesize >= 6 )
		;
	else
		return IP0REGION_ERROR_FILEHEADER_REVERED;
	
	if( (*ctx)->image_filesize >= 8 )
	{
		p_u16 = (uint16_t*)( (*ctx)->image_buffer + IP0REGION_V1_VERSION_OFFSET ) ;
		(*ctx)->version = NTOH16( *p_u16 ) ;
		if( (*ctx)->version == IP0REGION_V1 )
			;
		else
			return IP0REGION_ERROR_VERSION;
		
		if( (*ctx)->image_filesize >= IP0REGION_V1_FILEHEADER_SIZE )
		{
			p_u32 = (uint32_t*)( (*ctx)->image_buffer + IP0REGION_V1_REGION_SECTION_OFFSET ) ;
			(*ctx)->region_section_size = NTOH32( *p_u32 ) ;
			p_u32 = (uint32_t*)( (*ctx)->image_buffer + IP0REGION_V1_IP_SECTION_OFFSET ) ;
			(*ctx)->ip_section_size = NTOH32( *p_u32 ) ;
			(*ctx)->ip_count = (*ctx)->ip_section_size / IP0REGION_V1_IPBLOCK_SIZE ;
		}
		else
		{
			return IP0REGION_ERROR_FILE_TRUNCATED;
		}
	}
	else
	{
		return IP0REGION_ERROR_FILEHEADER_VERSION;
	}
	
	return 0;
}

static int CompareIpInBlock( struct Ip0regionContext *ctx , uint32_t ip , size_t ip_block_no , uint32_t *p_start_ip , uint32_t *p_end_ip , uint32_t *p_region_offset )
{
	uint32_t	*p_u32 = NULL ;
	uint32_t	start_ip ;
	uint32_t	end_ip ;
	
	p_u32 = (uint32_t*)( ctx->image_buffer + IP0REGION_V1_FILEHEADER_SIZE + ctx->region_section_size + ip_block_no * IP0REGION_V1_IPBLOCK_SIZE ) ;
	start_ip = NTOH32( *p_u32 ) ;
	p_u32++;
	end_ip = NTOH32( *p_u32 ) ;
	if( ip < start_ip )
	{
		return -1;
	}
	else if( ip > end_ip )
	{
		return 1;
	}
	else
	{
		(*p_start_ip) = start_ip ;
		(*p_end_ip) = end_ip ;
		p_u32++;
		(*p_region_offset) = NTOH32( *p_u32 ) ;
		return 0;
	}
}

static void DeserializeRegionInfo( struct Ip0regionContext *ctx , uint32_t start_ip , uint32_t end_ip , char **start_ip_str , char **end_ip_str , uint32_t region_offset , char **country_name , char **area_name , char **province_name , char **city_name , char **isp_name )
{
	uint8_t		*p_u8 = NULL ;
	char		*p_country_name = NULL ;
	uint8_t		country_name_len ;
	char		*p_area_name = NULL ;
	uint8_t		area_name_len ;
	char		*p_province_name = NULL ;
	uint8_t		province_name_len ;
	char		*p_city_name = NULL ;
	uint8_t		city_name_len ;
	char		*p_isp_name = NULL ;
	
	start_ip = HTON32( start_ip ) ;
	end_ip = HTON32( end_ip ) ;
	inet_ntop( AF_INET , (void*)&start_ip , __libip0region_start_ip_cache , sizeof(__libip0region_start_ip_cache) );
	inet_ntop( AF_INET , (void*)&end_ip , __libip0region_end_ip_cache , sizeof(__libip0region_end_ip_cache) );
	if( start_ip_str )
		(*start_ip_str) = __libip0region_start_ip_cache ;
	if( end_ip_str )
		(*end_ip_str) = __libip0region_end_ip_cache ;
	
	p_u8 = (uint8_t*)( ctx->image_buffer + region_offset ) ;
	country_name_len = (*p_u8) ;
	p_u8++;
	area_name_len = (*p_u8) ;
	p_u8++;
	province_name_len = (*p_u8) ;
	p_u8++;
	city_name_len = (*p_u8) ;
	
	p_country_name = ctx->image_buffer + region_offset + IP0REGION_V1_REGIONHEADER_SIZE ;
	p_area_name = p_country_name + country_name_len + 1 ;
	p_province_name = p_area_name + area_name_len + 1 ;
	p_city_name = p_province_name + province_name_len + 1 ;
	p_isp_name = p_city_name + city_name_len + 1 ;
	
	if( country_name )
		(*country_name) = p_country_name ;
	if( area_name )
		(*area_name) = p_area_name ;
	if( province_name )
		(*province_name) = p_province_name ;
	if( city_name )
		(*city_name) = p_city_name ;
	if( isp_name )
		(*isp_name) = p_isp_name ;
	
	return;
}

int QueryIp0regionImage( struct Ip0regionContext *ctx , char *ip_str , char **start_ip_str , char **end_ip_str , char **country_name , char **area_name , char **province_name , char **city_name , char **isp_name )
{
	struct in_addr	ip_addr ;
	uint32_t	ip ;
	size_t		min_ip_block_no ;
	size_t		max_ip_block_no ;
	size_t		try_ip_block_no ;
	uint32_t	start_ip ;
	uint32_t	end_ip ;
	uint32_t	region_offset ;
	int		compare ;
	
	int		nret = 0 ;
	
	nret = inet_pton( AF_INET , ip_str , & ip_addr ) ;
	if( nret != 1 )
		return IP0REGION_ERROR_IP_STR_FORMAT;
	ip = NTOH32( ip_addr.s_addr ) ;
	
	min_ip_block_no = 0 ;
	max_ip_block_no = ctx->ip_count - 1 ;
	
	compare = CompareIpInBlock( ctx , ip , min_ip_block_no , & start_ip , & end_ip , & region_offset ) ;
	if( compare == 0 )
	{
		DeserializeRegionInfo( ctx , start_ip , end_ip , start_ip_str , end_ip_str , region_offset , country_name , area_name , province_name , city_name , isp_name );
		return 0;
	}
	else if( compare == -1 )
	{
		return IP0REGION_ERROR_IP_NOT_FOUND;
	}
	
	compare = CompareIpInBlock( ctx , ip , max_ip_block_no , & start_ip , & end_ip , & region_offset ) ;
	if( compare == 0 )
	{
		DeserializeRegionInfo( ctx , start_ip , end_ip , start_ip_str , end_ip_str , region_offset , country_name , area_name , province_name , city_name , isp_name );
		return 0;
	}
	else if( compare == 1 )
	{
		return IP0REGION_ERROR_IP_NOT_FOUND;
	}
	
	for( ; ; )
	{
		try_ip_block_no = ( min_ip_block_no + max_ip_block_no ) / 2 ;
		if( try_ip_block_no == min_ip_block_no )
			return IP0REGION_ERROR_IP_NOT_FOUND;
		
		compare = CompareIpInBlock( ctx , ip , try_ip_block_no , & start_ip , & end_ip , & region_offset ) ;
		if( compare == 0 )
		{
			DeserializeRegionInfo( ctx , start_ip , end_ip , start_ip_str , end_ip_str , region_offset , country_name , area_name , province_name , city_name , isp_name );
			return 0;
		}
		else if( compare == -1 )
		{
			max_ip_block_no = try_ip_block_no ;
		}
		else if( compare == 1 )
		{
			min_ip_block_no = try_ip_block_no ;
		}
	}
}

void FreeIp0regionImage( struct Ip0regionContext **ctx )
{
	if( ctx == NULL )
		return;
	
	if( (*ctx) )
	{
		if( (*ctx)->image_filename )
			free( (*ctx)->image_filename );
		
		if( (*ctx)->image_buffer )
			free( (*ctx)->image_buffer );
		
		free( (*ctx) ); (*ctx) = NULL ;
	}
	
	return;
}

